﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace xyPlot
{
    /// <summary>
    /// A simple WPF Ticker graph, that displays all GraphDataItems
    /// within the ObservableCollection<GraphDataItem> property of this
    /// control. The X-Axis is obtained by taking the time period between
    /// the 1st and last GraphDataItem within the 
    /// ObservableCollection<GraphDataItem> property.
    /// 
    /// Whilst the Y scale uses the Min/Max of all items within the
    /// ObservableCollection<GraphDataItem> property
    /// 
    /// The new data will only be added to to the graph via an ObservableCollection
    /// if the Graph.CanAcceptNewStreamingReadings is set to true.
    /// The Graph can be panned left/right, and when it is panning it WILL
    /// not accept new values, as it is effectively in a paused state 
    /// </summary>
    public partial class Graph : UserControl
    {
        #region Data
        private Double maxValue = 0;
        private Double minValue = double.MaxValue;
        private Int32 MaxNumberOfItemsBeforePlottingStarts = 2;
        public static Boolean CanAcceptNewStreamingReadings = true;
        #endregion

        #region Ctor
        public Graph()
        {
            InitializeComponent();
            LastPointValue = new Point();
            DataItemXYPoints = new PointCollection();
            this.DataContext = this;

            MaxNumberOfItemsBeforePlottingStarts =
                xyPlot.Properties.Settings.Default.MAX_NUM_OF_ITEMS_BEFORE_PLOTTING_OCCCURS;

            if (MaxNumberOfItemsBeforePlottingStarts < 2)
                MaxNumberOfItemsBeforePlottingStarts = 2;

        }
        #endregion

        #region Public Methods

        /// <summary>
        /// Works out the actual X/Y points for the each value within the
        /// ObservableCollection<GraphDataItem> property.
        /// 
        /// There is a tolerance, such that there must be a certain amount
        /// of items seen before plotting starts. This is easily changed using
        /// the MaxNumberOfItemsBeforePlottingStarts const. 
        /// </summary>
        public void ObtainPointsForValues()
        {
            //Clear old points
            if (this.DataItemXYPoints != null)
                this.DataItemXYPoints.Clear();

            //Only proceed if there are some actual values and there are enough values
            if (DataValues != null)
            {
                if (DataValues.Count < MaxNumberOfItemsBeforePlottingStarts)
                {
                    overallContainer.Visibility = Visibility.Hidden;
                    return;
                }

                #region MinMax
                //work out min/max for Y-Scale
                maxValue = 0;
                minValue = Double.MaxValue;
                foreach (var dataValue in DataValues)
                {
                    if (dataValue.DataValue > maxValue)
                        maxValue = dataValue.DataValue;
                    if (dataValue.DataValue < minValue)
                        minValue = dataValue.DataValue;
                }
                #endregion

                #region Workout Points
                Double scale = maxValue - minValue;

                Double valuePerPoint = container.ActualHeight / scale;
                Double constantOffset = container.ActualWidth / DataValues.Count;
                Double xOffSet = 0;

                ///for each item seen work out what the actual X/Y should be 
                ///based on a bit of Maths
                for (int i = 0; i < DataValues.Count; i++)
                {
                    Double trueDiff = DataValues[i].DataValue - minValue;
                    Double heightPx = trueDiff * valuePerPoint;
                    Double yValue = container.ActualHeight - heightPx;

                    this.DataItemXYPoints.Add(new Point(xOffSet, yValue));
                    xOffSet += constantOffset;
                }

                //Keep the last point so we can position the horizontal last position
                //line and ellipse (see XAML, and LastPointValue DP code)
                this.LastPointValue = this.DataItemXYPoints.Last();

                #endregion

                #region Do Labels
                //LHS Scale : Build up all the scale labels, showing 2 decimal places
                LeftScaleMax.Content = maxValue.ToString("N2");
                LeftScaleMin.Content = minValue.ToString("N2");
                LeftScaleCurrentValue.Content =
                    this.DataValues.Last().DataValue.ToString("N2");
                LeftScaleCurrentValue.SetValue(
                    Canvas.TopProperty, this.LastPointValue.Y);
                lblTimeSeriesLeft.Content =
                    this.DataValues.First().TimeSampled.ToLongTimeString();

                //RHS Scale : Build up all the scale labels, showing 2 decimal places
                RightScaleMax.Content = maxValue.ToString("N2");
                RightScaleMin.Content = minValue.ToString("N2");
                RightScaleCurrentValue.Content =
                    this.DataValues.Last().DataValue.ToString("N2");
                RightScaleCurrentValue.SetValue(
                    Canvas.TopProperty, this.LastPointValue.Y);
                lblTimeSeriesRight.Content =
                    this.DataValues.Last().TimeSampled.ToLongTimeString();
                #endregion

                //Add Polygon Points
                GraphLine.Points = DataItemXYPoints;

                //Got points now so show graph
                overallContainer.Visibility = Visibility.Visible;

            }
            else
            {
                //Got points now so show graph
                overallContainer.Visibility = Visibility.Hidden;
            }
        }



        #endregion

        #region Private Methods

        /// <summary>
        /// When the current collection of GraphDataItem changes, work out
        /// the actual X/Y point values based on the actual values
        /// </summary>
        private void DataValues_CollectionChanged(object sender, 
            System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (!IsPaused)
                ObtainPointsForValues();
        }
        #endregion

        #region DPs

        #region LastPointValue

        /// <summary>
        /// The last point within the collection of points
        /// </summary>
        public static readonly DependencyProperty LastPointValueProperty =
            DependencyProperty.Register("LastPointValue", typeof(Point), typeof(Graph),
                new FrameworkPropertyMetadata(default(Point),
                    new PropertyChangedCallback(OnLastPointValueChanged)));

        /// <summary>
        /// Gets or sets the LastPointValue property.  
        /// </summary>
        public Point LastPointValue
        {
            get { return (Point)GetValue(LastPointValueProperty); }
            set { SetValue(LastPointValueProperty, value); }
        }

        /// <summary>
        /// Handles changes to the LastPointValue property.
        /// </summary>
        private static void OnLastPointValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Graph graphObject = (Graph)d;
            Point p = (Point) e.NewValue;

            //set LastPointLine
            graphObject.LastPointLine.Y1 = p.Y;
            graphObject.LastPointLine.Y2 = p.Y;

            //set LastPointMarkerEllipse
            graphObject.LastPointMarkerEllipse.SetValue(Canvas.LeftProperty,
                p.X - (graphObject.LastPointMarkerEllipse.Width / 2));
            graphObject.LastPointMarkerEllipse.SetValue(Canvas.TopProperty,
                p.Y - (graphObject.LastPointMarkerEllipse.Height / 2));

        }

        #endregion

        #region DataValues

        /// <summary>
        /// All the actual GraphDataItem items
        /// </summary>
        public static readonly DependencyProperty DataValuesProperty =
            DependencyProperty.Register("DataValues",
            typeof(ThreadSafeObservableCollection<GraphDataItem>), typeof(Graph),
                new FrameworkPropertyMetadata((ThreadSafeObservableCollection<GraphDataItem>)null,
                    new PropertyChangedCallback(OnDataValuesChanged)));

        /// <summary>
        /// Gets or sets the DataValues property.  This dependency property 
        /// indicates ....
        /// </summary>
        public ThreadSafeObservableCollection<GraphDataItem> DataValues
        {
            get { return (ThreadSafeObservableCollection<GraphDataItem>)GetValue(DataValuesProperty); }
            set { SetValue(DataValuesProperty, value); }
        }

        /// <summary>
        /// Handles changes to the DataValues property.
        /// </summary>
        private static void OnDataValuesChanged(DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            Graph obj = (Graph)d;

            if (e.NewValue != null && e.OldValue != null)
            {
                //if we see a different actual ThreadSafeObservableCollection<GraphDataItem>
                //we should unhook the old event handler
                if (!Object.ReferenceEquals(e.OldValue, e.NewValue))
                {
                    ThreadSafeObservableCollection<GraphDataItem> oldDataValues =
                        (ThreadSafeObservableCollection<GraphDataItem>)e.OldValue;
                    oldDataValues.CollectionChanged -= obj.DataValues_CollectionChanged;

                    ThreadSafeObservableCollection<GraphDataItem> newDataValues =
                        (ThreadSafeObservableCollection<GraphDataItem>)e.OldValue;
                    newDataValues.CollectionChanged += obj.DataValues_CollectionChanged;
                }
                else
                {
                    ThreadSafeObservableCollection<GraphDataItem> newDataValues =
                        (ThreadSafeObservableCollection<GraphDataItem>)e.NewValue;
                    newDataValues.CollectionChanged += obj.DataValues_CollectionChanged;
                }
            }
            else
            {
                ThreadSafeObservableCollection<GraphDataItem> newDataValues =
                    (ThreadSafeObservableCollection<GraphDataItem>)e.NewValue;
                newDataValues.CollectionChanged += obj.DataValues_CollectionChanged;
            }
        }
        #endregion

        #region DataItemXYPoints

        /// <summary>
        /// The X/Y points for all the graph points Dependency Property
        /// </summary>
        public static readonly DependencyProperty DataItemXYPointsProperty =
            DependencyProperty.Register("DataItemXYPoints", typeof(PointCollection), typeof(Graph),
                new FrameworkPropertyMetadata((PointCollection)null));

        /// <summary>
        /// Gets or sets the DataItemXYPoints property.  
        /// </summary>
        public PointCollection DataItemXYPoints
        {
            get { return (PointCollection)GetValue(DataItemXYPointsProperty); }
            set { SetValue(DataItemXYPointsProperty, value); }
        }

        #endregion

        #region GraphTitle

        /// <summary>
        /// The graph title
        /// </summary>
        public static readonly DependencyProperty GraphTitleProperty =
            DependencyProperty.Register("GraphTitle", typeof(String), typeof(Graph),
                new FrameworkPropertyMetadata((String)String.Empty,
                    new PropertyChangedCallback(OnGraphTitleChanged)));

        /// <summary>
        /// Gets or sets the GraphTitle property.  
        /// </summary>
        public String GraphTitle
        {
            get { return (String)GetValue(GraphTitleProperty); }
            set { SetValue(GraphTitleProperty, value); }
        }

        /// <summary>
        /// Handles changes to the GraphTitle property.
        /// </summary>
        private static void OnGraphTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Graph)d).lblTitle.Content = (String)e.NewValue;
        }



        #endregion

        #region IsPaused

        /// <summary>
        /// IsPaused Dependency Property
        /// </summary>
        public static readonly DependencyProperty IsPausedProperty =
            DependencyProperty.Register("IsPaused", typeof(bool), typeof(Graph),
                new FrameworkPropertyMetadata((bool)false, 
                    new PropertyChangedCallback(OnIsPausedChanged)));

        /// <summary>
        /// Gets or sets the IsPaused property.  
        /// </summary>
        public bool IsPaused
        {
            get { return (bool)GetValue(IsPausedProperty); }
            set { SetValue(IsPausedProperty, value); }
        }

        /// <summary>
        /// Handles changes to the IsPaused property.
        /// </summary>
        private static void OnIsPausedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            bool IsPaused = (bool)e.NewValue;
            Graph.CanAcceptNewStreamingReadings = IsPaused ? false : true;
        }


        #endregion
    
        #endregion
    }
}
